#!/usr/bin/env bash

set -euo pipefail

PYTHON_VERSION="2.7.18"
PYTHON_URL="https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tar.xz"
VIRTUALENV_VERSION="16.7.12"

SCRIPT_NAME=$(basename "$0")

usage() {
  cat <<EOF
Usage: ${SCRIPT_NAME} [--prefix DIR] [--venv DIR] [--build DIR]
                 [--offline]

Options:
  --prefix DIR  Install Python under DIR (default: ./python2-install)
  --venv DIR    Create the virtual environment at DIR (default: ./python2-venv)
  --build DIR   Use DIR as the build workspace (default: ./python2-build)
  --offline     Do not access the network; expect required artifacts locally
  -h, --help    Show this help message and exit

The script downloads Python ${PYTHON_VERSION} sources, builds them locally, and
creates an isolated virtual environment that can be activated to use Python 2.
EOF
}

log() {
  printf '==> %s\n' "$*"
}

make_absolute() {
  case "$1" in
    /*) printf '%s\n' "$1" ;;
    *) printf '%s/%s\n' "$(pwd)" "$1" ;;
  esac
}

require_commands() {
  local missing=()
  for cmd in "$@"; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
      missing+=("$cmd")
    fi
  done

  if [ "${#missing[@]}" -ne 0 ]; then
    printf 'Error: Missing required command(s): %s\n' "${missing[*]}" >&2
    exit 1
  fi
}

PREFIX="$(pwd)/python2-install"
VENV_DIR="$(pwd)/python2-venv"
BUILD_DIR="$(pwd)/python2-build"
OFFLINE=0

while [ "$#" -gt 0 ]; do
  case "$1" in
    --prefix)
      [ "$#" -ge 2 ] || { printf 'Error: --prefix requires a value\n' >&2; exit 1; }
      PREFIX="$2"
      shift 2
      ;;
    --venv)
      [ "$#" -ge 2 ] || { printf 'Error: --venv requires a value\n' >&2; exit 1; }
      VENV_DIR="$2"
      shift 2
      ;;
    --build)
      [ "$#" -ge 2 ] || { printf 'Error: --build requires a value\n' >&2; exit 1; }
      BUILD_DIR="$2"
      shift 2
      ;;
    --offline)
      OFFLINE=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      printf 'Error: Unknown option %s\n' "$1" >&2
      usage >&2
      exit 1
      ;;
  esac
done

PREFIX=$(make_absolute "$PREFIX")
VENV_DIR=$(make_absolute "$VENV_DIR")
BUILD_DIR=$(make_absolute "$BUILD_DIR")

if [ "$OFFLINE" -eq 1 ]; then
  require_commands tar make gcc xz
else
  require_commands curl tar make gcc xz
fi

log "Using prefix: $PREFIX"
log "Virtualenv dir: $VENV_DIR"
log "Build workspace: $BUILD_DIR"

mkdir -p "$PREFIX" "$BUILD_DIR"
mkdir -p "$(dirname "$VENV_DIR")"
WHEEL_DIR="$BUILD_DIR/wheels"
mkdir -p "$WHEEL_DIR"

TARBALL="$BUILD_DIR/Python-${PYTHON_VERSION}.tar.xz"
SRC_DIR="$BUILD_DIR/Python-${PYTHON_VERSION}"

if [ ! -f "$TARBALL" ]; then
  if [ "$OFFLINE" -eq 1 ]; then
    printf 'Error: Offline mode requires pre-downloaded tarball at %s\n' "$TARBALL" >&2
    exit 1
  fi
  log "Downloading Python ${PYTHON_VERSION} source..."
  curl -fL --progress-bar "$PYTHON_URL" -o "$TARBALL"
else
  log "Reusing existing tarball at $TARBALL"
fi

if [ ! -d "$SRC_DIR" ]; then
  log "Extracting source to $SRC_DIR"
  tar -xJf "$TARBALL" -C "$BUILD_DIR"
else
  log "Reusing source directory $SRC_DIR"
fi

if [ ! -x "$PREFIX/bin/python2.7" ]; then
  log "Configuring Python ${PYTHON_VERSION}"
  pushd "$SRC_DIR" >/dev/null
  ./configure --prefix="$PREFIX"
  JOBS=1
  if command -v nproc >/dev/null 2>&1; then
    JOBS=$(nproc)
  fi
  log "Building with $JOBS job(s)"
  make -j "$JOBS"
  log "Installing to $PREFIX"
  make install
  popd >/dev/null
else
  log "Python already installed at $PREFIX/bin/python2.7, skipping build"
fi

PYTHON_BIN="$PREFIX/bin/python2.7"
PIP_BIN="$PREFIX/bin/pip2"

if [ ! -x "$PYTHON_BIN" ]; then
  printf 'Error: Expected %s but it was not found\n' "$PYTHON_BIN" >&2
  exit 1
fi

log "Bootstrapping pip"
"$PYTHON_BIN" -m ensurepip --default-pip

if [ ! -x "$PIP_BIN" ]; then
  PIP_BIN="$PREFIX/bin/pip"
fi

if [ "$OFFLINE" -eq 1 ]; then
  log "Offline mode: skipping pip and setuptools upgrade"
else
  log "Upgrading pip and setuptools"
  "$PYTHON_BIN" -m pip install --upgrade pip setuptools
fi

if [ "$OFFLINE" -eq 1 ]; then
  VIRTUALENV_SPEC="virtualenv==${VIRTUALENV_VERSION}"
  VIRTUALENV_WHEEL=""
  for candidate in "$WHEEL_DIR"/virtualenv-"${VIRTUALENV_VERSION}"-*.whl; do
    if [ -f "$candidate" ]; then
      VIRTUALENV_WHEEL="$candidate"
      break
    fi
  done
  if [ -z "$VIRTUALENV_WHEEL" ]; then
    printf 'Error: Offline mode requires %s wheel under %s\n' "$VIRTUALENV_SPEC" "$WHEEL_DIR" >&2
    exit 1
  fi
  log "Installing virtualenv from local wheel $VIRTUALENV_WHEEL"
  "$PYTHON_BIN" -m pip install --no-index --find-links="$WHEEL_DIR" "$VIRTUALENV_SPEC"
else
  log "Installing virtualenv"
  "$PYTHON_BIN" -m pip install --upgrade virtualenv
fi

if [ -d "$VENV_DIR" ]; then
  log "Removing existing virtual environment at $VENV_DIR"
  if [ "$VENV_DIR" = "/" ]; then
    printf 'Error: Refusing to remove root directory\n' >&2
    exit 1
  fi
  rm -rf "$VENV_DIR"
fi

log "Creating Python 2 virtual environment"
"$PYTHON_BIN" -m virtualenv "$VENV_DIR"

cat <<EOF

Python 2 virtual environment created at:
  $VENV_DIR

Activate it with:
  source "$VENV_DIR/bin/activate"

Deactivate with:
  deactivate

EOF
